# AddInterfaceImplementation

import sys
import clr
import System
clr.AddReference('Microsoft.Scripting.dll')
clr.AddReference('IronPython.dll')
import IronPython
import Microsoft
import ClrWrapper
from System.Reflection.Emit import OpCodes
from ClrBuilder import Ldc_I4, Ldarg, TypeArray

class TestClass(object):
	def InstanceMethod(self):
		pass
	@classmethod
	def ClassMethod(cls):
		pass
	@staticmethod
	def StaticMethod():
		pass
	def GetMethod(self):
		return 0
	def SetMethod(self, value):
		pass
	property1 = property(GetMethod, SetMethod)
	property2 = property(GetMethod)

class TypeLoader(object):
	def __init__(self, filename):
		if filename.endswith('.py'):
			self.fileName = filename
			self.moduleName = filename[:-3]
		else:
			self.fileName = filename + '.py'
			self.moduleName = filename
	
	def GetClasses(self, module):
		classes = []
		for attributeName in dir(module):
			attribute = getattr(module, attributeName)
			if ClrWrapper.IsPythonClass(attribute):
				classes.append(attribute)
		return classes
	
	def GetInterfaces(self, typeObject):
		interfaces = []
		for interface in clr.GetClrType(typeObject).GetInterfaces():
			fullName = interface.FullName
			if fullName.startswith('IronPython.Runtime.'):
				continue
			if fullName.startswith('Microsoft.Scripting.'):
				continue
			if fullName == 'System.ComponentModel.ICustomTypeDescriptor':
				continue
			interfaces.append(clr.GetClrType(interface))
		return interfaces
	
	def GetExportedMethods(self, typeObject):
		exports = []
		for attributeName in dir(typeObject):
			attribute = getattr(typeObject, attributeName)
			if hasattr(attribute, 'clr_method') and attribute.clr_method:
				exports.append(attribute)
		return exports
	
	def GetExportedProperties(self, typeObject):
		exports = []
		for attributeName in dir(typeObject):
			attribute = getattr(typeObject, attributeName)
			if type(attribute) == ClrWrapper.clr_property:
				exports.append((attributeName, attribute))
		return exports
	
	def GetExportedClasses(self):
		if not sys.modules.has_key(self.moduleName):
			exec 'import ' + self.moduleName
		
		result = {}
		classes = self.GetClasses(sys.modules[self.moduleName])
		for theClass in classes:
			methods = self.GetExportedMethods(theClass)
			properties = self.GetExportedProperties(theClass)
			if len(methods) > 0 or len(properties) > 0:
				result[theClass] = (methods, properties)
		return result
	
	def GenerateWrapper(self, exportedClasses):
		gen = ModuleGenerator(self.fileName)
		gen.InitReferences()
		
		outputFile = self.moduleName + '.dll'
		moduleBuilder = gen.DefineAssembly(self.moduleName, outputFile)
		
		for cls in exportedClasses.keys():
			if hasattr(cls, 'clr_namespace'):
				namespace = cls.clr_namespace
			else:
				namespace = file
			interfaces = self.GetInterfaces(cls)
			typeBuilder = gen.DefineClass(moduleBuilder, namespace, cls.__name__, interfaces)
			gen.ApplyAttributes(cls, typeBuilder)
			gen.BuildClassInit(typeBuilder, cls.__name__)
			gen.BuildConstructor(typeBuilder)
			
			methods = exportedClasses[cls][0]
			properties = exportedClasses[cls][1]
			for fn in methods:
				if hasattr(fn, 'clr_accepts'):
					params = fn.clr_accepts
				else:
					params = ()
				
				if hasattr(fn, 'clr_returns'):
					resultType = fn.clr_returns
				else:
					resultType = None
				
				gen.BuildMethodHandler(typeBuilder, fn, resultType, params)
			
			for p in properties:
				name = p[0]
				readonly = (p[1].fset == TestClass.property2.fset)
				propertyBuilder = gen.BuildPropertyHandler(typeBuilder, p[1].propertyType, name, readonly)
				gen.ApplyAttributes(p[1], propertyBuilder)
			
			typeBuilder.CreateType()
		
		gen.Save(outputFile)

class ModuleGenerator(object):
	def __init__(self, pythonFile):
		self.pythonFile = pythonFile
		self.mainFunction = None
	
	def InitReferences(self):
		self.engine = clr.GetClrType(IronPython.Hosting.PythonEngine).GetField('CurrentEngine')
		engine2Cls = clr.GetClrType(Microsoft.Scripting.Hosting.ScriptEngine)
		self.createScriptSourceFromFile = engine2Cls.GetMethod('CreateScriptSourceFromFile', TypeArray(type('')))
		self.createScope = engine2Cls.GetMethod('CreateScope', TypeArray())
		for m in engine2Cls.GetMethods():
			if m.Name == 'Execute' and not m.IsGenericMethod:
				self.execute = m
		self.tryGetVariable = clr.GetClrType(Microsoft.Scripting.IScriptScope).GetMethod('TryGetVariable')
		self.objConstructor = clr.GetClrType(System.Object).GetConstructor(TypeArray())
		self.fromEngine = clr.GetClrType(Microsoft.Scripting.LanguageContext).GetMethod('FromEngine')
		self.scopeConstructor = clr.GetClrType(Microsoft.Scripting.Scope).GetConstructor(TypeArray(Microsoft.Scripting.LanguageContext))
		self.codeContextConstructor = clr.GetClrType(Microsoft.Scripting.CodeContext).GetConstructor(TypeArray(Microsoft.Scripting.Scope, Microsoft.Scripting.LanguageContext))
		self.createInstance = clr.GetClrType(IronPython.Runtime.Types.PythonType).GetMethod('CreateInstance', TypeArray(Microsoft.Scripting.CodeContext, System.Array[System.Object]))
		self.getOperations = engine2Cls.GetProperty('Operations').GetGetMethod()
		for m in clr.GetClrType(Microsoft.Scripting.Hosting.ObjectOperations).GetMethods():
			if m.Name == 'GetMember':
				p = m.GetParameters()
				if p[0].ParameterType.FullName == 'System.Object' and p[1].ParameterType.FullName == 'System.String':
					if m.IsGenericMethod:
						self.getMemberT = m
					else:
						self.getMember = m
			elif m.Name == 'SetMember' and m.IsGenericMethod:
				p = m.GetParameters()
				if p[0].ParameterType.FullName == 'System.Object' and p[1].ParameterType.FullName == 'System.String':
					self.setMemberT = m
		self.callOperation = clr.GetClrType(Microsoft.Scripting.Hosting.ObjectOperations).GetMethod('Call', TypeArray(System.Object, System.Array[System.Object]))
		self.convertOperation = clr.GetClrType(Microsoft.Scripting.Hosting.ObjectOperations).GetMethod('ConvertTo', TypeArray(System.Object))
		self.getCurrentManager = clr.GetClrType(Microsoft.Scripting.ScriptDomainManager).GetProperty('CurrentManager').GetGetMethod()
		self.getTypeFromHandle = clr.GetClrType(System.Type).GetMethod('GetTypeFromHandle')
		self.getAssembly = clr.GetClrType(System.Type).GetProperty('Assembly').GetGetMethod()
		self.loadAssembly = clr.GetClrType(Microsoft.Scripting.ScriptDomainManager).GetMethod('LoadAssembly')
		self.getDefaultPythonContext = clr.GetClrType(IronPython.Runtime.Calls.DefaultContext).GetProperty('DefaultPythonContext').GetGetMethod()
		self.getPAL = clr.GetClrType(Microsoft.Scripting.ScriptDomainManager).GetProperty('PAL').GetGetMethod()
		self.getCurrentDirectory = clr.GetClrType(Microsoft.Scripting.Hosting.PlatformAdaptationLayer).GetProperty('CurrentDirectory').GetGetMethod()
		self.addToPath = clr.GetClrType(IronPython.Runtime.PythonContext).GetMethod('AddToPath')
		a = clr.GetClrType(IronPython.Runtime.Operations.PythonTypeOps).Assembly
		typeCaller = a.GetType('IronPython.Runtime.Operations.PythonTypeOps+TypeCaller')
		self.typeCallerConstructor = typeCaller.GetConstructor(TypeArray(IronPython.Runtime.Types.PythonType))
		self.typeCallerCall = typeCaller.GetMethod('Call', TypeArray(Microsoft.Scripting.CodeContext, System.Array[System.Object]))
	
	def DefineAssembly(self, assemblyName, fileName):
		name = System.Reflection.AssemblyName();
		name.Name = assemblyName
		
		self.assemblyBuilder = System.AppDomain.CurrentDomain.DefineDynamicAssembly(
			name,
			System.Reflection.Emit.AssemblyBuilderAccess.Save
			)
		return self.assemblyBuilder.DefineDynamicModule(fileName, fileName)
	
	def DefineClass(self, moduleBuilder, namespace, className, interfaces):
		TypeAttributes = System.Reflection.TypeAttributes
		defineType = moduleBuilder.DefineType.Overloads[System.String, TypeAttributes, System.Type, System.Array[System.Type]]
		typeBuilder = defineType(
			namespace + '.' + className,
			TypeAttributes.Public,
			None, # parentType
			System.Array[System.Type](interfaces)
			)
		
		self.clsField = typeBuilder.DefineField(
			'theClass',
			IronPython.Runtime.Types.PythonType,
			System.Reflection.FieldAttributes.Static | System.Reflection.FieldAttributes.Private
			)
		self.objField = typeBuilder.DefineField(
			'theValue',
			System.Object,
			System.Reflection.FieldAttributes.Private
			)
		
		return typeBuilder
	
	def EmitAssemblyLoad(self, ilGenerator, theType):
		ilGenerator.Emit(OpCodes.Call, self.getCurrentManager)
		ilGenerator.Emit(OpCodes.Ldtoken, clr.GetClrType(theType))
		ilGenerator.Emit(OpCodes.Call, self.getTypeFromHandle)
		ilGenerator.Emit(OpCodes.Callvirt, self.getAssembly)
		ilGenerator.Emit(OpCodes.Callvirt, self.loadAssembly)
		ilGenerator.Emit(OpCodes.Pop)
	
	def EmitAddToPath(self, ilGenerator, directory=None):
		ilGenerator.Emit(OpCodes.Call, self.getDefaultPythonContext)
		ilGenerator.Emit(OpCodes.Call, self.getCurrentManager)
		ilGenerator.Emit(OpCodes.Callvirt, self.getPAL)
		if directory is None:
			ilGenerator.Emit(OpCodes.Callvirt, self.getCurrentDirectory)
		else:
			ilGenerator.Emit(OpCodes.Ldstr, directory)
		ilGenerator.Emit(OpCodes.Callvirt, self.addToPath)
	
	def BuildClassInit(self, typeBuilder, className):
		self.classInit = typeBuilder.DefineMethod(
			'ClassInit',
			System.Reflection.MethodAttributes.Static | System.Reflection.MethodAttributes.Private,
			System.Reflection.CallingConventions.Standard,
			None,
			TypeArray()
			)
		ilGenerator = self.classInit.GetILGenerator()
		
		source = ilGenerator.DeclareLocal(Microsoft.Scripting.Hosting.SourceUnit)
		scope = ilGenerator.DeclareLocal(Microsoft.Scripting.IScriptScope)
		value = ilGenerator.DeclareLocal(System.Object)
		
		self.EmitAssemblyLoad(ilGenerator, System.String)
		self.EmitAssemblyLoad(ilGenerator, System.Diagnostics.Debug)
		
		self.EmitAddToPath(ilGenerator)
		
		ilGenerator.Emit(OpCodes.Ldsfld, self.engine)
		ilGenerator.Emit(OpCodes.Ldstr, self.pythonFile)
		ilGenerator.Emit(OpCodes.Callvirt, self.createScriptSourceFromFile)
		ilGenerator.Emit(OpCodes.Stloc, source)
		
		ilGenerator.Emit(OpCodes.Ldsfld, self.engine)
		ilGenerator.Emit(OpCodes.Callvirt, self.createScope)
		ilGenerator.Emit(OpCodes.Stloc, scope)
		
		ilGenerator.Emit(OpCodes.Ldsfld, self.engine)
		ilGenerator.Emit(OpCodes.Ldloc, scope)
		ilGenerator.Emit(OpCodes.Ldloc, source)
		ilGenerator.Emit(OpCodes.Callvirt, self.execute)
		ilGenerator.Emit(OpCodes.Pop)
		
		ilGenerator.Emit(OpCodes.Ldloc, scope)
		ilGenerator.Emit(OpCodes.Ldstr, className)
		ilGenerator.Emit(OpCodes.Ldloca_S, value)
		ilGenerator.Emit(OpCodes.Callvirt, self.tryGetVariable)
		ilGenerator.Emit(OpCodes.Pop)
		
		ilGenerator.Emit(OpCodes.Ldloc, value)
		ilGenerator.Emit(OpCodes.Isinst, IronPython.Runtime.Types.PythonType)
		ilGenerator.Emit(OpCodes.Stsfld, self.clsField)
		
		ilGenerator.Emit(OpCodes.Ret)
	
	def EmitClassInit(self, ilGenerator):
		label1 = ilGenerator.DefineLabel()
		
		ilGenerator.Emit(OpCodes.Ldsfld, self.clsField)
		ilGenerator.Emit(OpCodes.Ldnull)
		ilGenerator.Emit(OpCodes.Ceq)
		ilGenerator.Emit(OpCodes.Ldc_I4_0)
		ilGenerator.Emit(OpCodes.Ceq)
		ilGenerator.Emit(OpCodes.Brtrue_S, label1)
		ilGenerator.Emit(OpCodes.Call, self.classInit)
		ilGenerator.MarkLabel(label1)
	
	def BuildConstructor(self, typeBuilder):
		constructorBuilder = typeBuilder.DefineConstructor(
			System.Reflection.MethodAttributes.Public,
			System.Reflection.CallingConventions.Standard,
			TypeArray()
			)
		ilGenerator = constructorBuilder.GetILGenerator()
		
		langContext = ilGenerator.DeclareLocal(Microsoft.Scripting.LanguageContext)
		scope = ilGenerator.DeclareLocal(Microsoft.Scripting.Scope)
		codeContext = ilGenerator.DeclareLocal(Microsoft.Scripting.CodeContext)
		
		ilGenerator.Emit(OpCodes.Ldarg_0)
		ilGenerator.Emit(OpCodes.Call, self.objConstructor)
		
		self.EmitClassInit(ilGenerator)
		
		ilGenerator.Emit(OpCodes.Ldsfld, self.engine)
		ilGenerator.Emit(OpCodes.Call, self.fromEngine)
		ilGenerator.Emit(OpCodes.Stloc, langContext)
		
		ilGenerator.Emit(OpCodes.Ldloc, langContext)
		ilGenerator.Emit(OpCodes.Newobj, self.scopeConstructor)
		ilGenerator.Emit(OpCodes.Stloc, scope)
		
		ilGenerator.Emit(OpCodes.Ldloc, scope)
		ilGenerator.Emit(OpCodes.Ldloc, langContext)
		ilGenerator.Emit(OpCodes.Newobj, self.codeContextConstructor)
		ilGenerator.Emit(OpCodes.Stloc, codeContext)
		
		ilGenerator.Emit(OpCodes.Ldarg_0)
		ilGenerator.Emit(OpCodes.Ldsfld, self.clsField)
		ilGenerator.Emit(OpCodes.Newobj, self.typeCallerConstructor)
		ilGenerator.Emit(OpCodes.Ldloc, codeContext)
		ilGenerator.Emit(OpCodes.Ldc_I4_0)
		ilGenerator.Emit(OpCodes.Newarr, System.Object)
		ilGenerator.Emit(OpCodes.Callvirt, self.typeCallerCall)
		ilGenerator.Emit(OpCodes.Stfld, self.objField)
		
		ilGenerator.Emit(OpCodes.Ret)
	
	def ApplyAttributes(self, pythonObject, builderObject):
		if hasattr(pythonObject, 'clr_attributes'):
			for a in pythonObject.clr_attributes:
				attr = System.Reflection.Emit.CustomAttributeBuilder(
					ClrWrapper.GetClrType(a[0]).GetConstructor(TypeArray()),
					()
					)
				builderObject.SetCustomAttribute(attr)
	
	def BuildMethodHandler(self, typeBuilder, fn, resultType, params):
		if type(fn) == type(TestClass.InstanceMethod) and fn.im_self is None:
			methodAttributes = System.Reflection.MethodAttributes.Public
			callingConventions = System.Reflection.CallingConventions.HasThis
			static = False
		else:
			methodAttributes = System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.Static
			callingConventions = System.Reflection.CallingConventions.Standard
			static = True
		
		method = typeBuilder.DefineMethod(
			fn.__name__,
			methodAttributes,
			callingConventions,
			resultType,
			System.Array[System.Type](params)
			)
		self.ApplyAttributes(fn, method)
		
		if static and fn.__name__ == 'Main':
			self.mainFunction = method
		
		ilGenerator = method.GetILGenerator()
		
		theFunction = ilGenerator.DeclareLocal(System.Object)
		result = ilGenerator.DeclareLocal(System.Object)
		args = ilGenerator.DeclareLocal(System.Array[System.Object])
		
		if static:
			self.EmitClassInit(ilGenerator)
		
		ilGenerator.Emit(OpCodes.Ldsfld, self.engine)
		ilGenerator.Emit(OpCodes.Callvirt, self.getOperations)
		if not static:
			ilGenerator.Emit(OpCodes.Ldarg_0)
			ilGenerator.Emit(OpCodes.Ldfld, self.objField)
		else:
			ilGenerator.Emit(OpCodes.Ldsfld, self.clsField)
		ilGenerator.Emit(OpCodes.Ldstr, fn.__name__)
		ilGenerator.Emit(OpCodes.Callvirt, self.getMember)
		ilGenerator.Emit(OpCodes.Stloc, theFunction)
		ilGenerator.Emit(OpCodes.Ldsfld, self.engine)
		ilGenerator.Emit(OpCodes.Callvirt, self.getOperations)
		ilGenerator.Emit(OpCodes.Ldloc, theFunction)
		
		Ldc_I4(ilGenerator, len(params))
		ilGenerator.Emit(OpCodes.Newarr, System.Object)
		ilGenerator.Emit(OpCodes.Stloc, args)
		
		paramNo = 0
		for param in params:
			ilGenerator.Emit(OpCodes.Ldloc, args)
			Ldc_I4(ilGenerator, paramNo)
			paramNo = paramNo + 1
			Ldarg(ilGenerator, paramNo)
			if param.IsValueType:
				ilGenerator.Emit(OpCodes.Box, param)
			ilGenerator.Emit(OpCodes.Stelem_Ref)
		
		ilGenerator.Emit(OpCodes.Ldloc, args)
		ilGenerator.Emit(OpCodes.Callvirt, self.callOperation)
		ilGenerator.Emit(OpCodes.Stloc, result)
		
		if resultType is None:
			pass
		elif resultType is System.Object:
			ilGenerator.Emit(OpCodes.Ldloc, result)
		else:
			ilGenerator.Emit(OpCodes.Ldsfld, self.engine)
			ilGenerator.Emit(OpCodes.Callvirt, self.getOperations)
			ilGenerator.Emit(OpCodes.Ldloc, result)
			convertOp = self.convertOperation.MakeGenericMethod(TypeArray(resultType))
			ilGenerator.Emit(OpCodes.Callvirt, convertOp)
		
		ilGenerator.Emit(OpCodes.Ret)
	
	def EmitGetter(self, typeBuilder, propertyType, propertyName):
		getterAttributes = (
			System.Reflection.MethodAttributes.Public |
			System.Reflection.MethodAttributes.SpecialName |
			System.Reflection.MethodAttributes.HideBySig
			)
		getterBuilder = typeBuilder.DefineMethod(
			'get_' + propertyName,
			getterAttributes,
			System.Reflection.CallingConventions.HasThis,
			propertyType,
			System.Type.EmptyTypes
			)
		ilGenerator = getterBuilder.GetILGenerator()
		
		ilGenerator.Emit(OpCodes.Ldsfld, self.engine)
		ilGenerator.Emit(OpCodes.Callvirt, self.getOperations)
		ilGenerator.Emit(OpCodes.Ldarg_0)
		ilGenerator.Emit(OpCodes.Ldfld, self.objField)
		ilGenerator.Emit(OpCodes.Ldstr, propertyName)
		getMember = self.getMemberT.MakeGenericMethod(TypeArray(propertyType))
		ilGenerator.Emit(OpCodes.Callvirt, getMember)
		ilGenerator.Emit(OpCodes.Ret)
		
		return getterBuilder
	
	def EmitSetter(self, typeBuilder, propertyType, propertyName):
		setterAttributes = (
			System.Reflection.MethodAttributes.Public |
			System.Reflection.MethodAttributes.SpecialName |
			System.Reflection.MethodAttributes.HideBySig
			)
		setterBuilder = typeBuilder.DefineMethod(
			'set_' + propertyName,
			setterAttributes,
			System.Reflection.CallingConventions.HasThis,
			None,
			TypeArray(propertyType)
			)
		ilGenerator = setterBuilder.GetILGenerator()
		
		ilGenerator.Emit(OpCodes.Ldsfld, self.engine)
		ilGenerator.Emit(OpCodes.Callvirt, self.getOperations)
		ilGenerator.Emit(OpCodes.Ldarg_0)
		ilGenerator.Emit(OpCodes.Ldfld, self.objField)
		ilGenerator.Emit(OpCodes.Ldstr, propertyName)
		ilGenerator.Emit(OpCodes.Ldarg_1)
		setMember = self.setMemberT.MakeGenericMethod(TypeArray(propertyType))
		ilGenerator.Emit(OpCodes.Callvirt, setMember)
		ilGenerator.Emit(OpCodes.Ret)
		
		return setterBuilder
	
	def BuildPropertyHandler(self, typeBuilder, propertyType, propertyName, readonly=False):
		empty = TypeArray()
		propertyBuilder = typeBuilder.DefineProperty(
			propertyName,
			System.Reflection.PropertyAttributes.None,
			System.Reflection.CallingConventions.HasThis,
			propertyType,
			None,
			None,
			None,
			None,
			None
			)
		propertyBuilder.SetGetMethod(self.EmitGetter(typeBuilder, propertyType, propertyName))
		if not readonly:
			propertyBuilder.SetSetMethod(self.EmitSetter(typeBuilder, propertyType, propertyName))
		return propertyBuilder
	
	def Save(self, fileName):
		if self.mainFunction is not None:
			self.assemblyBuilder.SetEntryPoint(self.mainFunction, System.Reflection.Emit.PEFileKinds.ConsoleApplication)
		self.assemblyBuilder.Save(fileName)

if __name__ == '__main__':
	loader = TypeLoader(sys.argv[1])
	c = loader.GetExportedClasses()
	loader.GenerateWrapper(c)
